package data

import (
	"context"
	goerror "errors"
	"github.com/go-kratos/kratos-layout/internal/biz"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/google/uuid"
	"github.com/pkg/errors"
	"io"
	"strings"

	"github.com/google/wire"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"

	"github.com/go-redis/redis/v8"

	"github.com/go-kratos/kratos-layout/internal/conf"
	"github.com/go-kratos/kratos-layout/internal/data/model"
	clientv3 "go.etcd.io/etcd/client/v3"
)

// ProviderSet is service providers.
var ProviderSet = wire.NewSet(
	NewGreeterRepo,
)

// ProviderSet is service providers.
var MapperSet = wire.NewSet(
	model.NewGreeterMapper,
	NewDB,
	NewETCD,
)

var CacheSet = wire.NewSet(
	NewGreeterCache,
	NewCache,
)

var CleanUpSet = wire.NewSet(
	NewBaseCleanUp,
)

func NewDB(c *conf.Data, up biz.CleanUp) *gorm.DB {
	db, err := gorm.Open(mysql.Open(c.Database.Source), &gorm.Config{
		DisableForeignKeyConstraintWhenMigrating: true,
	})
	if err != nil {
		panic("failed to connect database")
	}

	sc, _ := db.DB()

	up.RegisterAfterStop("database", sc)
	return db
}

func NewETCD(c *conf.Data, up biz.CleanUp) *clientv3.Client {
	client, err := clientv3.New(clientv3.Config{
		Endpoints: c.Etcd.AddrList,
	})
	if err != nil {
		panic(err)
	}

	//close on app exit
	up.RegisterAfterStop("etcd", client)

	return client
}

func NewCache(c *conf.Data, up biz.CleanUp) redis.UniversalClient {
	var client redis.UniversalClient
	if strings.ToLower(c.Redis.Type) == "cluster" {
		client = redis.NewClusterClient(&redis.ClusterOptions{
			Addrs:    strings.Split(c.Redis.Addr, ","), //set redis cluster url
			Password: c.Redis.Password,                 //set password
		})

		_, err := client.Ping(context.Background()).Result()

		if err != nil {
			panic(err)
		}
	} else {
		client = redis.NewClient(&redis.Options{
			Addr:     c.Redis.Addr,     //set redis cluster url
			Password: c.Redis.Password, //set password
			DB:       int(c.Redis.Index),
		})

		_, err := client.Ping(context.Background()).Result()

		if err != nil {
			panic(err)
		}
	}

	//close on app exit
	up.RegisterAfterStop("redis", client)
	return client
}

func NewFakeCache(c *conf.Data) redis.UniversalClient {
	return &redis.Client{}
}

func NewFakeDB(c *conf.Data) *gorm.DB {
	return &gorm.DB{}
}

type baseCleanUp struct {
	bm map[string]io.Closer
	am map[string]io.Closer
}

func (b *baseCleanUp) BeforeStop(ctx context.Context) error {
	var err error
	for k, v := range b.bm {
		err = v.Close()
		if err != nil {
			log.Info(errors.Wrap(err, "close "+k+" error"))
		} else {
			log.Info("close ", k)
		}
		err = goerror.Join(err)
	}
	return err
}

func (b *baseCleanUp) AfterStop(ctx context.Context) error {
	var err error
	for k, v := range b.am {
		err = v.Close()
		if err != nil {
			log.Info(errors.Wrap(err, "close "+k+" error"))
		} else {
			log.Info("close ", k)
		}
		err = goerror.Join(err)
	}
	return err
}

// r resource
func (b *baseCleanUp) RegisterAfterStop(r string, c io.Closer) {
	rn := r + "-" + uuid.NewString()
	b.bm[rn] = c
}

// r resource
func (b *baseCleanUp) RegisterBeforeStop(r string, c io.Closer) {
	rn := r + "-" + uuid.NewString()
	b.bm[rn] = c
}

func NewBaseCleanUp() biz.CleanUp {
	return &baseCleanUp{
		bm: make(map[string]io.Closer),
		am: make(map[string]io.Closer),
	}
}
